﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace production
{
    internal enum NodeTypes : byte {Material=0,Expense =1};
    internal class NodeInfo
    {
        public NodeInfo(string _productCode,decimal _value)
        { 
            ProductCode = _productCode;
            Value = _value;
        }
        public NodeInfo(string _productCode, string _taskCode, decimal _value)
        {
            ProductCode = _productCode;
            TaskCode = _taskCode;
            Value = _value;
        }
        public NodeInfo(string _productCode, string _taskCode, string _accountNo, decimal _value)
        {
            ProductCode = _productCode;
            TaskCode = _taskCode;
            AccountNo = _accountNo;
            Value = _value;
            NodeType = NodeTypes.Expense;
        }
        public string TaskCode = null;
        public string ProductCode = "";
        public string AccountNo = "";
        public decimal Value = 0;
        public NodeTypes NodeType = NodeTypes.Material;
        public bool isPointer = false;
        public override string ToString()
        {
            return SysLibs.ValidateString(this.ProductCode) + " , " +
                    SysLibs.ValidateString(this.TaskCode) + " , " +
                    SysLibs.ValidateString(this.Value.ToString());
        }
    }
    public class prodBuilding : TreeView 
    {
        private ProductionErrors myErrors = new ProductionErrors();
        private data.productionDS.prodTasksDataTable myTaskCodeTbl = new data.productionDS.prodTasksDataTable();
        private data.productionDS.prodTaskTemplateInDataTable myTemplateInTbl = new data.productionDS.prodTaskTemplateInDataTable();
        private data.productionDS.prodTaskTemplateOutDataTable myTemplateOutTbl = new data.productionDS.prodTaskTemplateOutDataTable();
        private data.productionDS.prodTaskTemplateExpenseDataTable myTemplateExpenseTbl = new data.productionDS.prodTaskTemplateExpenseDataTable();
        private DataView myTemplateOutByItemCode = null, myTemplateOutByTask_ItemCode = null,
                         myTemplateOutByTaskCode = null, myTemplateInByTaskCode = null, myTemplateExpenseTaskCode = null;

        public prodBuilding()
        {
            this.myTemplateInByTaskCode = new DataView(myTemplateInTbl);
            this.myTemplateInByTaskCode.Sort = myTemplateInTbl.taskCodeColumn.ColumnName;

            this.myTemplateOutByItemCode = new DataView(myTemplateOutTbl);
            this.myTemplateOutByItemCode.Sort = myTemplateOutTbl.itemCodeColumn.ColumnName;

            this.myTemplateOutByTaskCode  = new DataView(myTemplateOutTbl);
            this.myTemplateOutByTaskCode.Sort = myTemplateOutTbl.taskCodeColumn.ColumnName;

            this.myTemplateOutByTask_ItemCode = new DataView(myTemplateOutTbl);
            this.myTemplateOutByTask_ItemCode.Sort = myTemplateOutTbl.taskCodeColumn.ColumnName + "," + myTemplateOutTbl.itemCodeColumn.ColumnName;

            this.myTemplateExpenseTaskCode = new DataView(myTemplateExpenseTbl);
            this.myTemplateExpenseTaskCode.Sort = myTemplateExpenseTbl.taskCodeColumn.ColumnName;
        }
        private void LoadData()
        {
            this.myTaskCodeTbl.Clear();
            DataAccess.DataLibs.LoadData(myTaskCodeTbl);

            this.myTemplateInTbl.Clear();
            DataAccess.DataLibs.LoadData(myTemplateInTbl, (uint)common.CommonStatus.Enabled);

            this.myTemplateOutTbl.Clear();
            DataAccess.DataLibs.LoadData(myTemplateOutTbl, (uint)common.CommonStatus.Enabled);

            this.myTemplateExpenseTbl.Clear();
            DataAccess.DataLibs.LoadData(myTemplateExpenseTbl, (uint)common.CommonStatus.Enabled);

        }
        public void Reset()
        {
            this.myErrors.Clear();
        }
        public string GetErrorText()
        {
            return this.myErrors.ToString();
        }
        public void Build()
        {
            LoadData();
            this.Nodes.Clear();
            BuildTree_Material();
            BuildTree_Expense();
        }

        private void BuildTree_Material()
        {
            string key;
            TreeNode productNode = null, productTaskNode = null;
            TreeNode[] foundNodes;
            for (int idx = 0; idx < myTemplateOutTbl.Count; idx++)
            {
                foundNodes = this.Nodes.Find(myTemplateOutTbl[idx].itemCode, false);
                if (foundNodes.Length == 0)
                {
                    productNode = this.Nodes.Add(myTemplateOutTbl[idx].itemCode, myTemplateOutTbl[idx].itemCode);
                    productNode.Tag = new NodeInfo(myTemplateOutTbl[idx].itemCode, 0);
                }
                else productNode = foundNodes[0];

                key = MakeKey(myTemplateOutTbl[idx].itemCode, myTemplateOutTbl[idx].taskCode);
                foundNodes = productNode.Nodes.Find(key, true);
                if (foundNodes.Length == 0)
                {
                    productTaskNode = productNode.Nodes.Add(key, key);
                    productTaskNode.Tag = new NodeInfo(myTemplateOutTbl[idx].itemCode, myTemplateOutTbl[idx].taskCode, 0);
                    BuildSubTree_Material(productTaskNode, myTemplateOutTbl[idx]);
                }
            }
        }
        private void BuildTree_Expense( )
        {
            for (int idx = 0; idx < this.myTaskCodeTbl.Count; idx++)
            { 
                BuildTree_Expense(this.myTaskCodeTbl[idx].code);
            }
        }
        private void BuildTree_Expense(string taskCode)
        {
            DataRowView[] foundTemplateRows, foundExpenseRows;
            data.productionDS.prodTaskTemplateOutRow templateOutRow;
            data.productionDS.prodTaskTemplateExpenseRow expenseRow;

            // Calculate total weight for distribution
            foundTemplateRows = myTemplateOutByTaskCode.FindRows(taskCode);
            decimal totalWeight = 0,price;
            for (int idx = 0; idx < foundTemplateRows.Length; idx++)
            {
                templateOutRow = (data.productionDS.prodTaskTemplateOutRow)foundTemplateRows[idx].Row;
                price = ProductionLibs.GetPlanPrice(templateOutRow.itemCode);
                totalWeight += templateOutRow.amtRatio * (price == 0 ? 1 : price);
            }

            // Distribution
            decimal value, totalValue;
            TreeNode prodTaskNode,node,lastNode = null;
            foundExpenseRows = myTemplateExpenseTaskCode.FindRows(taskCode);
            for (int idx1 = 0; idx1 < foundExpenseRows.Length; idx1++)
            {
                expenseRow = (data.productionDS.prodTaskTemplateExpenseRow)foundExpenseRows[idx1].Row;
                totalValue = 0;
                for (int idx2 = 0; idx2 < foundTemplateRows.Length; idx2++)
                {
                    templateOutRow = (data.productionDS.prodTaskTemplateOutRow)foundTemplateRows[idx2].Row;
                    price = ProductionLibs.GetPlanPrice(templateOutRow.itemCode);
                    value = templateOutRow.amtRatio * (price == 0 ? 1 : price) / totalWeight;
                    value = Math.Round(expenseRow.amount * value,Settings.sysPrecisionAmt);
                    totalValue += value;
                    prodTaskNode = FindNode(templateOutRow.itemCode, templateOutRow.taskCode);
                    if (prodTaskNode == null)
                    {
                        myErrors.Add_TaskItem(ProductionErrors.Codes.ProductTaskNotExisted, templateOutRow.taskCode, templateOutRow.itemCode);
                        continue;
                    }
                    node = prodTaskNode.Nodes.Add(expenseRow.accountNo, expenseRow.accountNo.Trim() + " , " + value.ToString());
                    node.Tag = new NodeInfo(templateOutRow.itemCode, templateOutRow.taskCode, expenseRow.accountNo.Trim(), value);
                    lastNode = node;
                }
                if (lastNode != null && totalValue != expenseRow.amount)
                {
                    NodeInfo nodeInfo = (NodeInfo)lastNode.Tag;
                    nodeInfo.Value += expenseRow.amount - totalValue;
                }
            }
        }

        private void BuildSubTree_Material(TreeNode productTaskNode, data.productionDS.prodTaskTemplateOutRow outTemplateRow)
        {
            string key;
            NodeInfo nodeInfo;
            TreeNode node, childNode;
            DataRowView[] itemFoundRows1, itemFoundRows2;
            data.productionDS.prodTaskTemplateInRow itemTemplateInRow;
            data.productionDS.prodTaskTemplateOutRow itemTemplateOutRow;

            string productCode = (productTaskNode.Tag as NodeInfo).ProductCode;
            string taskCode = (productTaskNode.Tag as NodeInfo).TaskCode;
            itemFoundRows1 = myTemplateInByTaskCode.FindRows(taskCode);
            for (int idx1 = 0; idx1 < itemFoundRows1.Length; idx1++)
            {
                itemTemplateInRow = (data.productionDS.prodTaskTemplateInRow)itemFoundRows1[idx1].Row;
                //Find and ignore circular dependence
                if (IsCircular(productTaskNode, itemTemplateInRow))
                {
                    myErrors.Add_TaskItem(ProductionErrors.Codes.ProdTaskCircular, itemTemplateInRow.taskCode, itemTemplateInRow.itemCode);
                    continue;
                }
                //Add node 
                TreeNode[] foundNodes;

                // If node does NOT specify the source task, add 2 kinds of node
                // - One parrent node for the product itself
                // - On or many child nodes pointing to the node that contain its components
                if (itemTemplateInRow.IsfromTaskCodeNull())
                {
                    foundNodes = productTaskNode.Nodes.Find(itemTemplateInRow.itemCode, false);
                    if (foundNodes.Length == 0)
                    {
                        node = productTaskNode.Nodes.Add(itemTemplateInRow.itemCode, itemTemplateInRow.itemCode);
                        nodeInfo = new NodeInfo(itemTemplateInRow.itemCode, 0);
                        node.Tag = nodeInfo;
                    }
                    else
                    {
                        node = foundNodes[0];
                        nodeInfo = (NodeInfo)node.Tag;
                    }
                    if (outTemplateRow.qty != 0)
                        nodeInfo.Value += Math.Round((itemTemplateInRow.qty * outTemplateRow.qtyRatio) / outTemplateRow.qty,Settings.sysPrecisionQty);
                    node.Tag = nodeInfo;
                    node.Text = nodeInfo.ToString();

                    //Add child node 
                    itemFoundRows2 = myTemplateOutByItemCode.FindRows(itemTemplateInRow.itemCode);
                    for (int idx2 = 0; idx2 < itemFoundRows2.Length; idx2++)
                    {
                        itemTemplateOutRow = (data.productionDS.prodTaskTemplateOutRow)itemFoundRows2[idx2].Row;
                        key = MakeKey(itemTemplateOutRow.itemCode, itemTemplateOutRow.taskCode);
                        foundNodes = node.Nodes.Find(key, true);
                        if (foundNodes.Length == 0)
                        {
                            childNode = node.Nodes.Add(key, key);
                            nodeInfo = new NodeInfo(itemTemplateOutRow.itemCode, itemTemplateOutRow.taskCode, 0);
                            nodeInfo.isPointer = true;
                            childNode.Tag = nodeInfo;
                        }
                    }
                }
                else
                {
                    nodeInfo = (NodeInfo)productTaskNode.Tag;
                    if (nodeInfo.ProductCode != itemTemplateInRow.itemCode)
                        continue;

                    // If node DOES specify the source task, add only one pointer node 
                    // pointing to the node that contain its components
                    if (myTemplateOutByTask_ItemCode.FindRows(new object[] { itemTemplateInRow.fromTaskCode, itemTemplateInRow.itemCode }).Length == 0)
                    {
                        myErrors.Add_TaskItem(ProductionErrors.Codes.ProductTaskNotExisted, itemTemplateInRow.fromTaskCode, itemTemplateInRow.itemCode);
                        continue;
                    }
                    key = MakeKey(itemTemplateInRow.itemCode, itemTemplateInRow.fromTaskCode);
                    foundNodes = productTaskNode.Nodes.Find(key, false);
                    if (foundNodes.Length == 0)
                    {
                        node = productTaskNode.Nodes.Add(key, key);
                        nodeInfo = new NodeInfo(itemTemplateInRow.itemCode, itemTemplateInRow.fromTaskCode, 0);
                        nodeInfo.isPointer = true;
                        node.Tag = nodeInfo;
                    }
                }
            }
        }

        public bool BuildProductionRatioDB()
        {
            data.productionDS.prodRatioMaterialDataTable materialRatioTbl = new data.productionDS.prodRatioMaterialDataTable();
            data.productionDS.prodRatioExpenseDataTable expenseRatioTbl = new data.productionDS.prodRatioExpenseDataTable();
            NodeInfo processNodeInfo;
            for (int idx1 = 0; idx1 < this.Nodes.Count; idx1++)
            {
                for (int idx2 = 0; idx2 < this.Nodes[idx1].Nodes.Count; idx2++)
                {
                    processNodeInfo = (NodeInfo)this.Nodes[idx1].Nodes[idx2].Tag;
                    CalculateRatio(processNodeInfo.ProductCode, processNodeInfo.TaskCode,
                                   processNodeInfo.ProductCode, processNodeInfo.TaskCode, null, null, 1, materialRatioTbl, expenseRatioTbl);
                }
            }
            if (this.myErrors.Count == 0)
            {
                DataAccess.DataLibs.Delete_All_ProdRatioMaterial();
                DataAccess.DataLibs.UpdateData(materialRatioTbl);

                DataAccess.DataLibs.Delete_All_ProdRatioExpense();
                DataAccess.DataLibs.UpdateData(expenseRatioTbl);
                return true;
            }
            return false;
        }

        private TreeNode FindNode(string productCode, string taskCode)
        {
            TreeNode[] foundNodes = this.Nodes.Find(productCode, false);
            if (foundNodes.Length == 0) return null;
            foundNodes = foundNodes[0].Nodes.Find(MakeKey(productCode, taskCode), false);
            if (foundNodes.Length == 0) return null;
            return foundNodes[0];
        }

        private void CalculateRatio(string toProductCode, string toTaskCode, string itemCode, string taskCode, 
                                    string subProductCode, string subTaskCode,decimal qty, 
                                    data.productionDS.prodRatioMaterialDataTable materialTbl,
                                    data.productionDS.prodRatioExpenseDataTable expenseTbl)
        {
            NodeInfo processNodeInfo, nodeInfo;
            TreeNode productTaskNode, processNode;
            productTaskNode = FindNode(itemCode, taskCode);
            if (productTaskNode == null)
            {
                this.myErrors.Add_TaskItem(ProductionErrors.Codes.ProductTaskNotExisted, taskCode, itemCode);
                return;
            }

            for (int idx1 = 0; idx1 < productTaskNode.Nodes.Count; idx1++)
            {
                processNodeInfo = (NodeInfo)productTaskNode.Nodes[idx1].Tag;
                processNode = productTaskNode.Nodes[idx1];
                if (processNodeInfo.isPointer)
                {
                    CalculateRatio(toProductCode, toTaskCode, processNodeInfo.ProductCode, processNodeInfo.TaskCode,
                                 processNodeInfo.ProductCode, processNodeInfo.TaskCode, qty, materialTbl, expenseTbl);
                    continue;
                }
                //Leave node
                if (processNode.Nodes.Count == 0)
                {
                    if (processNodeInfo.NodeType == NodeTypes.Material)   
                        UpdateMaterial(toProductCode, processNodeInfo.ProductCode, toTaskCode,subProductCode,subTaskCode, qty * processNodeInfo.Value, materialTbl);
                    else
                        UpdateExpense(toProductCode, processNodeInfo.AccountNo, toTaskCode, subProductCode, subTaskCode, qty * processNodeInfo.Value, expenseTbl);
                    continue;
                }
                //Not a leave node
                for (int idx2 = 0; idx2 < processNode.Nodes.Count; idx2++)
                {
                    nodeInfo = (NodeInfo)processNode.Nodes[idx2].Tag;
                    if (nodeInfo.isPointer)
                    {
                        CalculateRatio(toProductCode, toTaskCode, nodeInfo.ProductCode, nodeInfo.TaskCode,
                                       nodeInfo.ProductCode, nodeInfo.TaskCode, qty * processNodeInfo.Value, materialTbl, expenseTbl);
                    }
                    else
                    {
                        if (nodeInfo.NodeType == NodeTypes.Material)   
                            UpdateMaterial(toProductCode, nodeInfo.ProductCode, toTaskCode, subProductCode, subTaskCode, qty * processNodeInfo.Value * nodeInfo.Value, materialTbl);
                        else 
                            UpdateExpense(toProductCode, nodeInfo.AccountNo, toTaskCode, subProductCode, subTaskCode, qty * processNodeInfo.Value * nodeInfo.Value, expenseTbl);
                    }
                }
            }
        }
        private void UpdateMaterial(string productCode, string itemCode, string taskCode, string subItemCode, string subTaskCode, decimal qty,
                                    data.productionDS.prodRatioMaterialDataTable toTbl)
        {
            if (qty == 0) return;
            data.productionDS.prodRatioMaterialRow dataRow;
            dataRow = toTbl.NewprodRatioMaterialRow();
            dataRow.productCode = productCode;
            dataRow.itemCode = itemCode;
            dataRow.taskCode = taskCode;
            dataRow.subProductCode = subItemCode;
            dataRow.subTaskCode = subTaskCode;
            dataRow.qty = qty;
            toTbl.AddprodRatioMaterialRow(dataRow);
        }

        private void UpdateExpense(string productCode, string accountNo, string taskCode, string subItemCode, string subTaskCode, decimal amt,
                                   data.productionDS.prodRatioExpenseDataTable toTbl)
        {
            if (amt == 0) return;
            data.productionDS.prodRatioExpenseRow dataRow;
            dataRow = toTbl.NewprodRatioExpenseRow();
            dataRow.productCode = productCode;
            dataRow.accountNo = accountNo;
            dataRow.taskCode = taskCode;
            dataRow.subProductCode = subItemCode;
            dataRow.subTaskCode = subTaskCode;
            dataRow.amt = amt;
            toTbl.AddprodRatioExpenseRow(dataRow);
        }

        //Find and ignore circular dependence
        private bool IsCircular(TreeNode node, data.productionDS.prodTaskTemplateInRow templateRow)
        {
            while (true)
            {
                NodeInfo nodeInfo = (NodeInfo)node.Tag;
                if (templateRow.IsfromTaskCodeNull())
                {
                    if (nodeInfo.ProductCode == templateRow.itemCode)
                        return true;
                }
                else
                {
                    if (nodeInfo.ProductCode == templateRow.itemCode && nodeInfo.TaskCode == templateRow.fromTaskCode)
                        return true;
                }
                if (node.Parent == null) break;
                node = node.Parent;
            }
            return false; 
        }
        private static string MakeKey(string itemCode,string taskCode)
        {
            return common.StringLibs.MakeString(",",itemCode, taskCode); 
        }
    }
}
